package org.jspar.temp;

import java.util.ArrayList;
import java.util.List;

import org.jspar.Constants;
import org.jspar.model.Terminal;
import org.jspar.route.LocalRoute;
import org.jspar.temp.Expansion.ExpansionDistance;
import org.jspar.tile.Position;
import org.jspar.tile.Tile;

public class Path {
	
	private int cost = -1;
	private Terminal terminal;
	private List<Tile> tiles = new ArrayList<Tile>();
	private Expansion expansion;

	public Path(Expansion expansion, Terminal terminal, Tile tile) {
		this(expansion, terminal);
		tiles.add(tile);
	}
	
	public Path(Expansion expansion, Terminal terminal) {
		this.expansion = expansion;
		this.terminal = terminal;
	}
	
	public void resetCost() {
		cost = -1;
	}

	public int cost() {
		if (cost < 0)
			cost = new CostComputer().compute();
		return cost;
	}
	
	public class PointEnumerator {
		
		protected int index;
		protected Position corner;
		
		public PointEnumerator() {
			this.corner = new Position(-1, -1);
			this.index = tiles.size() - 1;
		}
		
		protected void shift() {
			index -= 1;
		}

		protected Tile getLastTile() {
			return index >= 0 ? tiles.get(index) : null;
		}

		protected Tile getCurTile() {
			return index-1 >= 0 ? tiles.get(index-1) : null;
		}
		
		public List<Position> computePoints() {
			List<Position> positions = new ArrayList<Position>();
			lengthToNextCorner(getLastTile(), getCurTile(), false);
			positions.add(corner);
			shift();
			while (getLastTile() != null) {
				if (getCurTile() != null) {
					lengthToNextCorner(getLastTile(), getCurTile(), true);
					positions.add(corner);
				} else {
					positions.add(terminal.getGlobalPosition());
				}
				shift();
			}
			return positions;
		}

		protected int lengthToNextCorner(Tile t1, Tile t2, boolean useOrg) {
			Position org = corner;
			if (t1 == null) {
				if (t2 == null)
					return 0;
				else {
					corner = t2.getBounds().center();
					if (!useOrg)
						return t2.width() / 2;
					return Math.abs(org.x() - t2.right());
				}
			}

			TileInfo a1 = (TileInfo) t1.content();
			if (t2 == null) {
				corner = t1.getBounds().center();
				if (!useOrg)
					return t1.width() / 2;
				return Math.abs(org.x() - t1.right());
			}
			
			TileInfo a2 = (TileInfo) t2.content();
			if (!a1.isHorizontal && a2.isHorizontal) {
				int[] q = LocalRoute.findIntersection(t1.x(), t1.right(), t2.y(), t2.bottom());
				int midpnt = (q[0] + q[1]) / 2;
				corner = new Position(t1.y() + (t1.height() / 2), midpnt);
				return !useOrg ? t1.width() / 2 : Math.abs(org.y() - midpnt);
			}
			
			if (a1.isHorizontal && !a2.isHorizontal) {
				int[] q = LocalRoute.findIntersection(t2.y(), t2.bottom(), t1.x(), t1.right());
				int midpnt = (q[0] + q[1]) / 2;
				corner = new Position(midpnt, t1.y() + (t1.height() / 2));
				return !useOrg ? t1.width() / 2 : Math.abs(org.x() - midpnt);
			}
			corner = t2.getBounds().center();
			if (a2.isHorizontal)
				return !useOrg ? (t2.height() + t1.height()) / 2 : Math.abs(org.y() - corner.y());
			return !useOrg ? (t2.width() + t1.width()) / 2 : Math.abs(org.x() - corner.x());
		}
		
	}
	
	private class CostComputer extends PointEnumerator {
		
		private int length;
		private int cornerCount;
		private int congestionCost;

		public CostComputer() {
			super();
			this.length = 0;
			this.cornerCount = 1;
		}
		
		public int compute() {
			lengthToNextCorner(getLastTile(), getCurTile(), false);
			ExpansionDistance ed = expansion.distanceToGo(corner);
			if (ed.expansion != null) {
				length = (int)(ed.distance * Constants.FORWARD_EST_MULTIPLIER);
			}
			TileInfo a = (TileInfo) getLastTile().content();
			congestionCost = a.congestionCost();
			shift();
			while (getLastTile() != null) {
				a = (TileInfo) getLastTile().content();
				congestionCost += a.congestionCost();
				if (getCurTile() != null) {
					length += lengthToNextCorner(getLastTile(), getCurTile(), true);
					cornerCount += 1;
				} else {
					length += corner.manhatanDistance(terminal.getGlobalPosition());
				}
				shift();
			}
			return length * Constants.LENGTH_WEIGHT +  cornerCount * Constants.CORNER_COST + congestionCost;
		}
		
	}
	
	public boolean contact(Tile tile) {
		TileInfo info = (TileInfo)tile.content();
		for (Tile t : tiles) {
			TileInfo i = (TileInfo)t.content();
			if (info.testContact(i)) {
				return true;
			}
		}
		return false;
	}

	public Tile lastTile() {
		return tiles.get(tiles.size()-1);
	}

	public Path add(Tile t) {
		Path newPath = new Path(expansion, terminal);
		newPath.tiles.addAll(tiles);
		newPath.tiles.add(t);
		return newPath;
	}

	public boolean contains(Tile t) {
		return tiles.contains(t);
	}

	public List<Tile> tiles() {
		return tiles;
	}
	
	public List<Position> getPoints() {
		return new PointEnumerator().computePoints();
	}
	
	@Override
	public String toString() {
		return "Path(" + terminal + "){" + cost + "}";
	}

}
